Skip to content

[sw] Pull-in MLDSA software impl for eg100#30552

Open
sasdf wants to merge 3 commits into
lowRISC:earlgrey_1.0.0from
sasdf:mjTmlqqtoxz
Open

[sw] Pull-in MLDSA software impl for eg100#30552
sasdf wants to merge 3 commits into
lowRISC:earlgrey_1.0.0from
sasdf:mjTmlqqtoxz

Conversation

@sasdf

@sasdf sasdf commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

This PR integrates the embedpqc software implementation of ML-DSA and updates the KMAC driver to support the required SHAKE operations.

The embedpqc library from Google's BoringSSL doesn't have public repo yet, so we pull the sources directly for now. This PR only pulls the mldsa-44 tiny-stack profile.

Key Changes

  1. ML-DSA Software Library (third_party/embedpqc):
    • Pulled in the core embedpqc library files.
    • Added a porting layer in third_party/embedpqc/ports/ that implements the SHAKE128 and SHAKE256 interfaces by wrapping the Silicon Creator KMAC driver.
  2. KMAC Driver Enhancements (sw/device/silicon_creator/lib/drivers/):
    • Added kmac_shake128_configure() to support SHAKE-128 mode.
    • Refactored squeezing logic to expose kmac_squeeze_words() and kmac_done(), allowing arbitrary-length squeezing required by ML-DSA.
  3. Verification Tests (sw/device/tests/embedpqc/):
    • Added test suites for ML-DSA-44-tiny using test vectors to verify correctness and measure stack usage.

Stack usage estimation:

Algorithm / Phase Variant KeyGen Sign Verify
ML-DSA-44 Tiny 9,908 B 13,492 B 11,268 B

@sasdf sasdf force-pushed the mjTmlqqtoxz branch 3 times, most recently from a394044 to 720ee09 Compare June 30, 2026 20:24
@sasdf sasdf requested review from cfrantz and siemen11 June 30, 2026 22:26
@sasdf sasdf marked this pull request as ready for review June 30, 2026 22:26
@sasdf sasdf requested a review from a team as a code owner June 30, 2026 22:26
@sasdf sasdf requested review from moidx and removed request for a team June 30, 2026 22:26
Comment on lines +16 to +28
register uint8_t *a0 asm("a0") = out_pk;
register const uint8_t *a1 asm("a1") = seed;
register void *a2 asm("a2") = stack_top;

asm volatile(
"mv s1, sp\n"
"mv sp, a2\n"
"call mldsa44_tiny_pub_from_seed\n"
"mv sp, s1\n"
: "+r"(a0), "+r"(a1), "+r"(a2)
:
: "ra", "s1", "a3", "a4", "a5", "a6", "a7", "t0", "t1", "t2", "t3", "t4",
"t5", "t6", "memory");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty advanced stuff :)

What does the codegen look like for this, and how does this compare with writing an actual assembly language entrypoint (e.g. mldsa44_tiny_caller.S)?

With an assembly-language entrypoint, we could simply obey the C-ABI register convention rather than having to mark it all as clobbered in the inline assembly statement. However, we'd have to appropriately manage whichever register is responsible for holding the original stack pointer so we can restore it after the call returns.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Hmm, I forgot the reason why I decided to switch from standalone asm to inline asm during prototyping...)

The PR is updated to use assembly-language entrypoint, and I also confirmed manually that unused functions will be stripped correctly.

Comment thread third_party/embedpqc/BUILD Outdated
Comment on lines +29 to +38
cc_library(
name = "mldsa44",
srcs = ["mldsa44.c"],
hdrs = ["mldsa44.h"],
deps = [
":ct",
":mldsa_mu",
"//third_party/embedpqc/ports:shake",
],
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we bringing in both the regular and tiny versions of the functions?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the tools for measuring latency on real chips now, maybe we can keep both variants for now and decide later?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is almost certain we only need tiny, no @cfrantz ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped all variants other than mldsa-44-tiny variant for this initial PR.

Comment thread third_party/embedpqc/ports/shake.c Outdated
@sasdf sasdf force-pushed the mjTmlqqtoxz branch 2 times, most recently from 5ab37dc to a586c10 Compare July 1, 2026 19:48

@siemen11 siemen11 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @sasdf, what a monster haha! I checked the assembly whether the parts that should be constant time are so, this looks to be the case

@johannheyszl

Copy link
Copy Markdown
Contributor

Can we add some cycle counts to the tiny executions maybe?

sasdf added 2 commits July 6, 2026 21:34
Using `.strip()` on the file content before splitting removes all
leading whitespaces of the entire file. By switching to `.rstrip()`,
we preserve any leading empty lines/spaces while still stripping
trailing whitespaces and ensuring the file ends with a single newline.

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: Ib9954c5ed78c1610b1148ee7913f786f6a6a6964
This patch exports the `kmac_squeeze_words` and `kmac_done` functions
from the kmac driver to allow caller to squeeze an arbitrary number of
words from the Keccak state incrementally.

Also, adds `kmac_shake128_configure` to support SHAKE-128 configuration.

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: Ide996b205dcd4cf41425571315cd0b496a6a6964
@sasdf sasdf force-pushed the mjTmlqqtoxz branch 3 times, most recently from 60eab91 to e0d0d6d Compare July 8, 2026 04:44
Import `embedpqc` library containing embedded friendly implementations
of ML-DSA-44, ML-DSA-65, and ML-DSA-87 signatures derived from BoringSSL.

This commit pulls in both the standard variants and the stack usage
optimized "tiny" variants of ML-DSA, integrated with OpenTitan's
hardware KMAC block for SHAKE acceleration.

The change also introduces smoke tests for each variant targeting the
Earlgrey chip.

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: Ide996b205dcd4cf41425571315cd0b496a6a6964

@nasahlpa nasahlpa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @sasdf for pulling this code into the repository.

I've focused my review on the fist two commits as well as the test code in sw/device/tests/embedpqc. This LGTM!

The only remark I have is that we should add a readme to third_party/embedpqc stating that this code is not hardened against SCA and FI attacks.

print(f'Binary file: "{path}"')
continue
new_text = "\n".join([line.rstrip() for line in old_text.strip().split("\n")]) + "\n"
new_text = "\n".join([line.rstrip() for line in old_text.rstrip().split("\n")]) + "\n"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change, the documentation at the top of the file is outdated:

# - There is no leading whitespace in the file.

}

if (launder32(offset) == kShake256KeccakRateWords) {
if (launder32(offset) == rate_words) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In cryptolib, we have:

if (launder32(offset) == keccak_rate_words && idx < digest_len_words) {

So here we are running an additional CMD.RUN after a last full block. This is fine.

rom_error_t kmac_shake256_squeeze_end(uint32_t *out, size_t outlen);

/**
* Squeeze arbitrary number of words from the Keccak state.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really an arbitrary number of words or should it be a multiple of rate_words? If a first call would ask for non-rate multiple, the next call to this function would then re-read those words? I think it might be worth documenting this here.

":mldsa_testvectors",
"//sw/device/lib/runtime:log",
"//sw/device/lib/testing/test_framework:ottf_main",
"//third_party/embedpqc/ports:mldsa44_tiny_caller",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"//third_party/embedpqc/ports:mldsa44_tiny_caller",
"//third_party/embedpqc/ports:mldsa44_tiny_caller",
"//third_party/embedpqc:mldsa44_tiny",

I think this could be added to make the includes more robust.

LOG_INFO("Sign Test Passed.");

// 3. Verify Test
mldsa44_tiny_pub_from_seed_with_stack(buf, kMldsa44SignSeed,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we compare here to a golden public key? On the other hand, if this fails, the verify below would also fail. So fine for me to leave it as it is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants